package club.kingyin.kyblog.core.utils.token;

import club.kingyin.kyblog.core.utils.kyutil.DateUtil;
import com.auth0.jwt.JWT;
import com.auth0.jwt.JWTCreator;
import com.auth0.jwt.JWTVerifier;
import com.auth0.jwt.algorithms.Algorithm;
import com.auth0.jwt.exceptions.JWTCreationException;
import com.auth0.jwt.exceptions.JWTVerificationException;
import com.auth0.jwt.interfaces.DecodedJWT;
import org.springframework.stereotype.Component;

import java.io.UnsupportedEncodingException;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;

/**
 * @author : kingyins
 * @version : 1.0
 * @program : Ky-Blog
 * @description : 在此处添加类描述
 * @create : 2021年08月19日 15:46
 **/

@Component
public final class TokenUtil {

    // 私钥
    private static String KEY;
    private static String issuer;
    private static long time;

    static {
        KEY = "29thd03";
        issuer = "club/kingyin";
        time = 86400000 * 15;
    }

    /**
     * 生成加密后的token
     * @param verify 是不是验证成功,true表示是成功，false表示失败。
     * @param username 用户名
     * @param id  id
     * @return 加密后的token
     */
    public static String getToken(final boolean verify, final String username, final String id) {
        Map<String, Object> map = new HashMap<>();
        map.put("verify", verify);
        map.put("username",username);
        map.put("id",id);
        return createToken(map,null);
    }

    // 获取对象所存的数据
    private static Map<String, Object> ObjectToMap(Object object) {
        Map<String, Object> map = new HashMap<>();
        // 判断是否使用了全局注解
        Class<?> aClass = object.getClass();
        boolean isAll = aClass.getAnnotation(Token.class) != null;
        Field[] fields = aClass.getDeclaredFields();
        for (Field field : fields) {
            String name = field.getName();
            // 判断是否使用了token注解带的value当作别名
            if (field.getAnnotation(Token.class) != null &&
                    !field.getAnnotation(Token.class).value().equals(""))
                name = field.getAnnotation(Token.class).value();
            field.setAccessible(true);  // 打开私有权限
            try {
                // 有全局注解或者单个注解才加入map
                TokenIgnore ignore = field.getAnnotation(TokenIgnore.class);
                if (ignore == null && (isAll || field.getAnnotation(Token.class) != null))
                    map.put(name,field.get(object));
                else {
                    if (ignore != null && ignore.remove()) {
                        field.set(object,null);
                    }
                }
            } catch (IllegalAccessException e) {
                // 在Object上没有找到相应的Token注解
                e.printStackTrace();
            }
        }
        return map;
    }

    // 创建token
    private static String createToken(Map<String, Object> map, Date expires) {
        String token = null;
        try {
            if (expires == null)
                expires = new Date(System.currentTimeMillis() + time);
            JWTCreator.Builder builder = JWT.create();
            builder.withIssuer(issuer)
                    .withExpiresAt(expires);
            map.forEach((s, o) -> {
                builder.withClaim(s,String.valueOf(o));
            });
            // 使用了HMAC256加密算法。
            // mysecret是用来加密数字签名的密钥。
            token = builder.sign(Algorithm.HMAC256(KEY));
        } catch (JWTCreationException exception){
            //Invalid Signing configuration / Couldn't convert Claims.
        } catch (IllegalArgumentException | UnsupportedEncodingException e) {
            e.printStackTrace();
        }
        return token;
    }

    public static String getToken(Object object, Date expires) {
        return createToken(ObjectToMap(object), expires);
    }

    /**
     * 生成加密后的token 可以用@Token注释该类</br>
     * 用@IgnoreToken注释该类中不需要生成令牌的参数</br>
     * @param object 对某个对象生产token
     * @return token
     * @throws IllegalAccessException
     */
    public static String getToken(Object object) {
        return getToken(object,null);
    }

    /**
     * 生成保存对象的token</br>
     * 永久有效
     * @param object 要保存的对象
     * @return 字符串
     */
    public static String serialization(Object object) {
        Date perpetual = new Date(DateUtil.toStamp(DateUtil.custom("2099-12-31")));  // 长期有效
        return createToken(ObjectToMap(object),perpetual);
    }

    /**
     * 先验证token是否被伪造，然后解码token。
     * @param token 字符串token
     * @return 解密后的DecodedJWT对象，可以读取token中的数据。
     */
    public static DecodedJWT deToken(final String token) {
        DecodedJWT jwt = null;
        try {
            // 使用了HMAC256加密算法。
            // mysecret是用来加密数字签名的密钥。
            JWTVerifier verifier = JWT.require(Algorithm.HMAC256(KEY))
                    .withIssuer(issuer)
                    .build(); //Reusable verifier instance
            jwt = verifier.verify(token);
        } catch (JWTVerificationException | IllegalArgumentException | UnsupportedEncodingException exception){
            // Invalid signature/claims
            // 验证失败返回ull
            return null;
        }
        return jwt;
    }

    /**
     * 解析token为对象</br>
     * 之能解析对象中八大数据类型，其他类型不能解释</br>
     * @param token 解析对象
     * @param clazz 解析目标对象
     * @param <T> 目标类型
     * @return 解析后的对象
     */
    public static <T> T deToken(String token, Class<T> clazz) {
        DecodedJWT jwt = deToken(token);
        try {
            T newT = clazz.getDeclaredConstructor().newInstance();
            Field[] fields = newT.getClass().getDeclaredFields();
            for (Field field : fields) {
                field.setAccessible(true);
                try {
                    if (jwt.getClaim(field.getName()).as(field.getType()) == null)
                        continue;
                    field.set(newT,jwt.getClaim(field.getName()).as(field.getType()));
                } catch (Exception e) {
                    continue;
                }
            }
            return newT;
        } catch (InstantiationException | IllegalAccessException | NoSuchMethodException | InvocationTargetException e) {
            e.printStackTrace();
        }
        return null;
    }

}

